Skip to content

fix(dates): allow clearing issue, cycle, and module dates - #255

Merged
martian56 merged 3 commits into
mainfrom
fix/clear-date-fields
Jul 5, 2026
Merged

fix(dates): allow clearing issue, cycle, and module dates#255
martian56 merged 3 commits into
mainfrom
fix/clear-date-fields

Conversation

@martian56

@martian56 martian56 commented Jul 5, 2026

Copy link
Copy Markdown
Member

What

Closes #126. Once a start/target/end date was set on a work item, cycle, or module, it could not be cleared. The handlers only parsed the date when it was a non-empty string, so both null and "" collapsed to "no change" and the service left the value untouched.

How

  • The date fields on the issue, cycle, and module PATCH endpoints now bind as json.RawMessage and go through a shared parseUpdatableDate helper with explicit tri-state semantics:
    • key absent -> leave unchanged
    • null or "" -> clear to NULL
    • "YYYY-MM-DD" (or RFC3339) -> set
  • Each service's Update takes a matching ...Set bool flag per date and assigns the pointer (nil clears) only when set, mirroring the existing estimate_point_id pattern. Callers (bulk update, epic update) pass false, nil for the untouched dates.

Testing

New internal/handler/clear_dates_test.go covers all three entities: set the dates, clear them with null (persists as NULL), and confirm that omitting a date leaves it unchanged. Full go test ./internal/handler ./internal/service green.

AI assistance

Produced with the help of Claude Code (Claude Opus 4.8). AI-assisted commits carry a Co-Authored-By trailer.

Summary by CodeRabbit

  • New Features
    • PATCH date fields for issues, cycles, and modules now distinguish between “leave unchanged” (omit) and “clear” (send null).
  • Bug Fixes
    • Date parsing is more explicit and accepts RFC3339 timestamps or YYYY-MM-DD; invalid start_date/target_date now return clearer 400 errors.
    • Date clearing behavior is consistent across issues, cycles, modules, and epic-related updates.
  • Tests
    • Added coverage to confirm cleared dates persist as NULL and omitted fields remain unchanged.

Once a start/target/end date was set it could not be cleared: the handlers only
parsed the field when it was a non-empty string, so both null and "" collapsed
to "no change" and the service left the value untouched.

The date fields on the issue, cycle, and module PATCH endpoints now bind as
json.RawMessage and go through a shared parseUpdatableDate helper with explicit
tri-state semantics: an absent key leaves the value unchanged, null or "" clears
it to NULL, and a YYYY-MM-DD (or RFC3339) value sets it. The services take a
matching "set" flag per date and assign the pointer (nil clears) only when set,
mirroring the existing estimate_point_id pattern.

Closes #126

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@martian56
martian56 requested a review from a team as a code owner July 5, 2026 08:18
@martian56 martian56 added this to the Finish w Enhancements milestone Jul 5, 2026
@martian56 martian56 added bug Something isn't working API UI labels Jul 5, 2026
@martian56 martian56 self-assigned this Jul 5, 2026
@strix-security

strix-security Bot commented Jul 5, 2026

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Updated for 7108379.


Reviewed by Strix
Re-run review · Configure security review settings

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 55beffca-610d-4fb5-8fd7-d46b9a72ffdd

📥 Commits

Reviewing files that changed from the base of the PR and between 10cfab7 and 7108379.

📒 Files selected for processing (2)
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/service/issue.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/service/issue.go

📝 Walkthrough

Walkthrough

This change adds PATCH semantics that distinguish absent, null, blank, and valued date fields for issues, cycles, and modules. Handlers pass explicit set flags to services, services clear fields when values are null, and tests verify persisted NULLs.

Changes

Clearable date fields

Layer / File(s) Summary
Shared date parsing helper
apps/api/internal/handler/dates.go
Adds parseUpdatableDate to interpret absent, null, blank, and valued date fields.
Issue date-clear support
apps/api/internal/handler/issue.go, apps/api/internal/service/issue.go, apps/api/internal/handler/epic.go
Changes start_date/target_date to json.RawMessage, parses via parseUpdatableDate, and threads startDateSet/targetDateSet flags through IssueService.Update, BulkUpdate, and EpicHandler.UpdateEpic.
Cycle date-clear support
apps/api/internal/handler/cycle.go, apps/api/internal/service/cycle.go
Changes start_date/end_date to json.RawMessage, parses via parseUpdatableDate, and adds startDateSet/endDateSet flags to CycleService.Update.
Module date-clear support
apps/api/internal/handler/module.go, apps/api/internal/service/module.go
Changes start_date/target_date to json.RawMessage, parses via parseUpdatableDate, and adds startDateSet/targetDateSet flags to ModuleService.Update.
API tests for clearing dates
apps/api/internal/handler/clear_dates_test.go
Adds TestIssue_ClearDates, TestCycle_ClearDates, TestModule_ClearDates verifying dates persist as NULL after PATCH with null.

Estimated code review effort: 4 (Complex) | ~55 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler
  participant parseUpdatableDate
  participant Service
  participant Database

  Client->>Handler: PATCH {date: null}
  Handler->>parseUpdatableDate: parse raw JSON value
  alt field absent
    parseUpdatableDate-->>Handler: set=false
    Handler->>Service: Update(dateSet=false)
    Service->>Database: leave date unchanged
  else field null or blank
    parseUpdatableDate-->>Handler: set=true, value=nil
    Handler->>Service: Update(dateSet=true, date=nil)
    Service->>Database: set date = NULL
  else field has value
    parseUpdatableDate-->>Handler: set=true, value=parsedTime
    Handler->>Service: Update(dateSet=true, date=parsedTime)
    Service->>Database: set date = parsedTime
  end
  Database-->>Service: persisted
  Service-->>Handler: updated entity
  Handler-->>Client: 200 OK
Loading

Related Issues: #126

Estimated code review effort: 4 (Complex) | ~55 minutes

Poem

A null once lost, a date once stuck,
Now clears away with rabbit luck! 🐇
RawMessage hops through JSON's gate,
Set-flags whisper "clear that date!"
NULL blooms fresh in DB soil —
No more dates that will not toil. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change, but it does not follow most required template sections like linked issues, type of change, and test plan. Restructure the PR description to match the template, including Summary, Linked issues, Type of change, Surface, What changed, Why this approach, and Test plan.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: allowing date fields to be cleared on issues, cycles, and modules.
Linked Issues check ✅ Passed The changes satisfy #126 by allowing issue, cycle, and module dates to be set, updated, omitted, and cleared to null with tests covering the behavior.
Out of Scope Changes check ✅ Passed The extra service, handler, and test updates are all supporting changes for the date-clearing fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clear-date-fields

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/api/internal/handler/clear_dates_test.go (1)

52-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cycle and module tests skip the "omit field leaves unchanged" case.

TestIssue_ClearDates verifies all three states (set, clear-via-null, omitted-leaves-unchanged), but TestCycle_ClearDates and TestModule_ClearDates only cover set and clear. The PR objective explicitly calls for tests covering "set, update, and clear behavior" for all three entities, and the omitted-field case is the one most likely to regress silently since it depends on the *Set flag correctly staying false.

✅ Suggested addition for TestCycle_ClearDates
 	var cleared model.Cycle
 	require.NoError(t, ts.DB.First(&cleared, "id = ?", cy.ID).Error)
 	require.Nil(t, cleared.StartDate, "cycle start_date should clear")
 	require.Nil(t, cleared.EndDate, "cycle end_date should clear")
+
+	// Omitting the field leaves it unchanged.
+	require.Equal(t, http.StatusOK, ts.PATCH(base, map[string]any{"start_date": "2026-04-01"}, w.Session).Code)
+	require.Equal(t, http.StatusOK, ts.PATCH(base, map[string]any{"name": "Renamed"}, w.Session).Code)
+	var kept model.Cycle
+	require.NoError(t, ts.DB.First(&kept, "id = ?", cy.ID).Error)
+	require.NotNil(t, kept.StartDate, "omitting start_date must not clear it")
 }

A similar addition applies to TestModule_ClearDates.

Also applies to: 76-98

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/clear_dates_test.go` around lines 52 - 74,
TestCycle_ClearDates currently covers only setting dates and clearing them with
null, but it is missing the “omitted field leaves value unchanged” case. Extend
TestCycle_ClearDates to perform an update where start_date and end_date are not
included, then assert the previously stored dates remain unchanged by reloading
the cycle via model.Cycle; apply the same pattern to TestModule_ClearDates using
the corresponding handler test and model.Module so all three states are
verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/internal/handler/cycle.go`:
- Line 154: The cycle PATCH handler is ignoring JSON binding errors, so
malformed payloads and bad field types can slip through as no-op updates. Update
the cycle update flow in the handler that calls c.ShouldBindJSON on body to
check and return a 400 response when binding fails, instead of discarding the
error. Use the existing PATCH handling path in the cycle handler to keep valid
requests unchanged while rejecting invalid request bodies early.

In `@apps/api/internal/service/issue.go`:
- Around line 574-578: The date-change logging in issue updates is skipping
clear events because the history/activity checks still gate on
startDate/targetDate being non-nil even when startDateSet or targetDateSet is
true. Update the logic around issue.StartDate and issue.TargetDate in the issue
update flow so the logging/notification conditions use the corresponding set
flags, allowing nil assignments to be recorded as clears. Use the existing
startDateSet and targetDateSet checks alongside the date fields in the issue
update path to locate and adjust the change-tracking conditions.

---

Nitpick comments:
In `@apps/api/internal/handler/clear_dates_test.go`:
- Around line 52-74: TestCycle_ClearDates currently covers only setting dates
and clearing them with null, but it is missing the “omitted field leaves value
unchanged” case. Extend TestCycle_ClearDates to perform an update where
start_date and end_date are not included, then assert the previously stored
dates remain unchanged by reloading the cycle via model.Cycle; apply the same
pattern to TestModule_ClearDates using the corresponding handler test and
model.Module so all three states are verified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 37bd387d-f76f-47e0-8b39-e479883d49e0

📥 Commits

Reviewing files that changed from the base of the PR and between 50a533b and 140eef5.

📒 Files selected for processing (9)
  • apps/api/internal/handler/clear_dates_test.go
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/handler/dates.go
  • apps/api/internal/handler/epic.go
  • apps/api/internal/handler/issue.go
  • apps/api/internal/handler/module.go
  • apps/api/internal/service/cycle.go
  • apps/api/internal/service/issue.go
  • apps/api/internal/service/module.go

Comment thread apps/api/internal/handler/cycle.go Outdated
Comment thread apps/api/internal/service/issue.go
CodeRabbit on PR #255:
- the issue activity/notification log gated start_date/target_date changes on
  the pointer being non-nil, so clearing a date was silently omitted from
  history; gate on the set flag instead so clears are recorded.
- the cycle Update handler ignored the JSON bind error, letting a malformed
  body fall through as a no-op; return 400 for bad JSON while still allowing an
  empty body, matching the module handler.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@nazarli-shabnam nazarli-shabnam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max acc privileges. jealous

@martian56
martian56 merged commit c4e5094 into main Jul 5, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Date fields cannot be cleared after they are set

2 participants